package com.github.slprj.slake.pb;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Des:
 * 解析pb工具类
 * &通常用于取值,和0x07, Ox7F使用
 * |通常用于 set值,通常在进行左移后,获取最高位 进行赋值
 *
 * @author captain
 * @date 2022/8/22 10:49
 */
public class PbUtil {

    //32 - 3 = 29  3位表示类型
    public static final int MAX_SUPPORT_FIELD_INDEX = 536870911;

    public static final int TYPE_VAR_INT = 0;
    public static final int TYPE_FIX64 = 1;
    public static final int TYPE_LENGTH_DELIMITED = 2;
    public static final int TYPE_START_GROUP = 3;
    public static final int TYPE_END_GROUP = 4;
    public static final int TYPE_FIX32 = 5;

    public static void writeTag(ByteBuffer buf, int type, int index) {
        if (index > MAX_SUPPORT_FIELD_INDEX || index <= 0) {
            throw new IllegalArgumentException(String.valueOf(index));
        }
        //type的后三个bit位表示类型
        int value = (index << 3) | type;
        writeVarUnsignedInt32(buf, value);
    }

    public static void parse(ByteBuffer buf, PbCallback callback) {
        ByteOrder old = buf.order();
        buf.order(ByteOrder.LITTLE_ENDIAN);
        int limit = buf.limit();
        while (buf.hasRemaining()) {
            int index = readVarUnsignedInt32(buf);
            // 0000 0111 , 与运算读取最后三位
            int type = index & 0x07;
            //右移3位,恢复index
            index >>>= 3;
            switch (type) {
                case TYPE_VAR_INT:
                    // TODO only support int32 now
                    callback.readInt(index, readVarUnsignedInt32(buf));
                    break;
                case TYPE_FIX32:
                    callback.readInt(index, buf.getInt());
                    break;
                case TYPE_FIX64:
                    callback.readLong(index, buf.getLong());
                    break;
                case TYPE_LENGTH_DELIMITED:
                    //对于字符串,首先获取它的key,再获取它的长度,再获取整个数据
                    int length = readVarUnsignedInt32(buf);
                    if (length > buf.remaining() || length < 0) {
                        throw new PbException("bad protobuf length: " + length);
                    }
                    int newLimit = buf.position() + length;
                    buf.limit(newLimit);
                    callback.readBytes(index, buf);
                    //设置当前位置
                    buf.position(newLimit);
                    buf.limit(limit);
                    break;
                case TYPE_START_GROUP:
                case TYPE_END_GROUP:
                default:
                    throw new PbException("protobuf type not support: " + type);
            }
        }
        buf.order(old);
    }


    public static void writeVarUnsignedInt32(ByteBuffer buf, int value) {
        if (value == 0) {
            throw new IllegalArgumentException();
        }
        for (int i = 0; i < 5; i++) {
            //每次用一个字节类装
            //获取最后的7位的值   0x7F->二进制 1111111
            int x = value & 0x7F;
            //读取后右移动7位,继续读
            value >>>= 7;
            if (value != 0) {
                //如果还存在值,则给x的最高位(第8位)设置为1 ,其他位置不变 , 0x80 二进制 1000 0000
                x |= 0x80;
                //最高为1 , 表示负 , 0则为正
                buf.put((byte) x);
            } else {
                buf.put((byte) x);
                return;
            }
        }
    }

    public static int readVarUnsignedInt32(ByteBuffer buf) {
        int bitIndex = 0;
        int value = 0;
        for (int i = 0; i < 5; i++) {
            //获取一个byte,首位为1,则<0,表明还有数据可以读取
            int x = buf.get();
            //1.x & 0x7F 读取当前字节的后七个字节
            //2.(x & 0x7F) << bitIndex:拼接每个byte位,第一次7个位置
            //如果未读取完成,则向左继续移动7个位置, |= 为了填充bit位
            //0000  0000000 0000000 0000000 0000000
            value |= (x & 0x7F) << bitIndex;
            if (x >= 0) {
                return value;
            }
            bitIndex += 7;
        }
        throw new PbException("bad protobuf var int input");
    }

    private static long readVarInt64(ByteBuffer buf) {
        int bitIndex = 0;
        long value = 0;
        for (int i = 0; i < 10; i++) {
            long x = buf.get();
            value |= (x & 0x7F) << bitIndex;
            if (x >= 0) {
                return value;
            }
            bitIndex += 7;
        }
        throw new PbException("bad protobuf var int input");
    }
}
